package my.webservices.security;

import java.util.Properties;
import java.util.Vector;

import javax.security.auth.callback.CallbackHandler;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;

import org.apache.ws.security.WSSecurityEngine;
import org.apache.ws.security.components.crypto.Crypto;
import org.apache.ws.security.components.crypto.CryptoFactory;
import org.apache.ws.security.message.WSSecEncrypt;
import org.apache.ws.security.message.WSSecHeader;
import org.apache.ws.security.message.WSSignEnvelope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;

public class EncryptUtil {

	private final static Logger LOGGER = LoggerFactory
			.getLogger(EncryptUtil.class);

	public static void checkSignatureAndDecode(SOAPMessage msg,
			CallbackHandler cb, Properties prop) throws Exception {

		WSSecurityEngine secEngine = new WSSecurityEngine();

		LOGGER.debug("prop before calling Cryptofactory ={}", prop);

		Crypto crypto = CryptoFactory.getInstance(prop);
		org.w3c.dom.Document doc = toDocument(msg);
		LOGGER.debug("Document = {}", doc);
		// after we set the encrypt stuff the processsecurity does all the work

		Vector v = secEngine.processSecurityHeader(doc, null, cb, crypto);

		if (v == null) {
			LOGGER.debug("Access not granted.");
			throw new Exception("Access not granted.");

		}

		LOGGER.debug("Vector = {}", v);

		if (LOGGER.isDebugEnabled()) {
			String outputString = org.apache.ws.security.util.XMLUtils
					.PrettyDocumentToString(doc);
			LOGGER.debug("Message: {}",
					outputString);
		}
		// put the decoded message into the object
		updateSOAPMessage(doc, msg);
		LOGGER.debug("After updating soap message. msg {}", msg);
		LOGGER.debug("returning.");
	}

	/**
	 * Updates the message with the unencrypt form
	 */

	private static SOAPMessage updateSOAPMessage(Document doc,
			SOAPMessage message) throws SOAPException {

		DOMSource domSource = new DOMSource(doc);
		message.getSOAPPart().setContent(domSource);

		return message;

	}

	/**
	 * Changes the SOAPMessage to a dom.Document.
	 */

	public static org.w3c.dom.Document toDocument(SOAPMessage soapMsg)
			throws SOAPException, TransformerException {

		Source src = soapMsg.getSOAPPart().getContent();

		TransformerFactory tf = TransformerFactory.newInstance();

		Transformer transformer = tf.newTransformer();

		DOMResult result = new DOMResult();
		transformer.transform(src, result);
		return (Document) result.getNode();

	}

	/**
	 * Signs a SOAP envelope according to the WS Specification (X509 profile)
	 * and adds the signature data to the envelope.
	 * 
	 * @param mensaje
	 * @throws Exception
	 */
	@SuppressWarnings("deprecation")
	public static void signSOAPEnvelope(SOAPMessage mensaje, Properties prop)
			throws Exception {

		WSSignEnvelope signer = new WSSignEnvelope();

		String alias = prop.getProperty("alias");
		String password = prop.getProperty("password");

		signer.setUserInfo(alias, password);

		Crypto crypto = CryptoFactory.getInstance(prop);

		Document doc = toDocument(mensaje);
		signer.setMustUnderstand(false);

		Document signedDoc = signer.build(doc, crypto);
		DOMSource domSource = new DOMSource(signedDoc);
		mensaje.getSOAPPart().setContent(domSource);

	}

	/**
	 * Encrypts a SOAPMessage.
	 */
	public static void encryptSOAPEnvelope(SOAPMessage msg, Properties prop)
			throws Exception {

		// WSSignEnvelope signs a SOAP envelope according to the
		// WS Specification (X509 profile) and adds the signature data
		// to the envelope.
		WSSecEncrypt encryptor = new WSSecEncrypt();

		String alias = prop.getProperty("alias");
		String password = prop.getProperty("password");

		encryptor.setUserInfo(alias, password);

		Crypto crypto = CryptoFactory.getInstance(prop);

		Document doc = toDocument(msg);

		WSSecHeader secHeader = new WSSecHeader();
		secHeader.insertSecurityHeader(doc);

		Document signedDoc = encryptor.build(doc, crypto, secHeader);

		DOMSource domSource = new DOMSource(signedDoc);
		msg.getSOAPPart().setContent(domSource);
	}
}
